'''
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License") you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http:#www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
'''
from base.datatypes.xsd.XSDDatatype import XSDDatatype
from base.datatypes.xsd.XSDDatatypeInstances import XSDDatatypeInstances
from base.datatypes.xsd.impl.XMLLiteralType import XMLLiteralType
from base.graph.LiteralLabel import LiteralLabel
from base.java.StringBuilder import StringBuilder
from base.java.exception.NullPointerException import NullPointerException
from base.rdf.impl.Util import Util
from base.shared import JenaParameters
from base.shared.DatatypeFormatException import DatatypeFormatException

#import java.util.Arrays;
#import java.util.Locale;
#import java.util.Objects;

#import org.apache.jena.JenaRuntime;
#import org.apache.jena.datatypes.*;
#import org.apache.jena.datatypes.xsd.*;
#import org.apache.jena.datatypes.xsd.impl.*;
#import org.apache.jena.rdf.model.impl.Util;
#import org.apache.jena.shared.JenaException;
#import org.apache.jena.shared.impl.JenaParameters;
#import org.apache.jena.vocabulary.RDF;
#import org.slf4j.Logger;
#import org.slf4j.LoggerFactory;

'''
 * Represents the "contents" of a Node_Literal.
 * These contents comprise a lexical form, an optional language tag,
 * and optional datatype structure and a value.
'''

'''
 * Return True if the literal label is a language string. (RDF 1.0 and RDF 1.1)
'''
# param LiteralLabel lit
# result static boolean
def isLangString(lit):
    # Duplicated by Util.isLangString except for the consistency check.
    # String
    lang = lit.language()
    if (lang is None):
        return False
    # Check.
    if (lang == ""):
        return False

    # TODO 后续可能需要校验类型
    # This is an additional check.
    # if (JenaRuntime.isRDF11):
    #     if (not Objects.equals(lit.getDatatype(), RDF.dtLangString))
    #         throw JenaException("Literal with language string which is not rdf:langString: " + lit)

    return True

'''
 * Return True if the literal lable is a string value (RDF 1.0 and RDF 1.1)
'''

# param LiteralLabel lit
# result static boolean
def isStringValue(lit):
    if (lit.getDatatype() is None):
        # RDF 1.0
        return not isLangString(lit)
    if (lit.getDatatype().equals(XSDDatatypeInstances.XSDstring)):
        return True
    return False


'''
     * Two literal labels are the "same value" if they are the same string,
     * or same language string or same value-by-datatype or .equals (= Same RDF Term)
     *
     * @param lit1
     * @param lit2
     * @return
    '''


# param LiteralLabel lit1, LiteralLabel lit2
# result static boolean
def sameValueAs(lit1, lit2):
    # return  lit1.sameValueAs(lit2)
    if (lit1 is None):
        raise NullPointerException(None)
    if (lit2 is None):
        raise NullPointerException(None)
    # Strings.
    if (isStringValue(lit1) and isStringValue(lit2)):
        # Complete compatibility mode.
        # 这里目前默认为True 所以下面的else 就不用走了
        if (JenaParameters.enablePlainLiteralSameAsString):
            return lit1.getLexicalForm().equals(lit2.getLexicalForm())
        # else:
        #     return lit1.getLexicalForm().equals(lit2.getLexicalForm()) and Objects.equals(lit1.getDatatype(), lit2.getDatatype())

    if (isStringValue(lit1)):
        return False
    if (isStringValue(lit2)):
        return False

    # Language tag strings
    if (isLangString(lit1) and isLangString(lit2)):
        # String
        lex1 = lit1.getLexicalForm()
        # String
        lex2 = lit2.getLexicalForm()
        return lex1.equals(lex2) and lit1.language().equalsIgnoreCase(lit2.language())

    if (isLangString(lit1)):
        return False
    if (isLangString(lit2)):
        return False

    # Both not strings, not lang strings.
    # Datatype set.
    if (lit1.isWellFormedRaw() and lit2.isWellFormedRaw()):
        # Both well-formed.
        return lit1.getDatatype().isEqual(lit1, lit2)
    if (not lit1.isWellFormedRaw() and not lit2.isWellFormedRaw()):
        return lit1.equals(lit2)
    # One is well formed, the other is not.
    return False

class LiteralLabelImpl(LiteralLabel):

    # param LiteralLabelImpl.class
    # result Logger log =
    # def LoggerFactory.getLogger(self)

    '''
     * The lexical form of the literal, may be None if the literal was
     * created programatically and has not yet been serialized
    '''
    # private String lexicalForm

    '''
     * The value form of the literal. It will be None only if the value
     * has not been parsed or if it is an illegal value.
     * For plain literals and xsd:string literals
     * the value is the same as the lexicalForm.
    '''
    # private Object value

    '''
     * The type of the literal. A None type indicates a classic "plain" literal.
     * The type of a literal is fixed when it is created.
    '''
    # private RDFDatatype dtype

    '''
     * The xml:lang tag. For xsd literals this is ignored and not part of
     * equality. For plain literals it is not ignored. The lang of a
     * literal is fixed when it is created.
    '''
    # '''final''' private String lang

    '''
     * Indicates whether this is a legal literal. The working groups requires
     * ill-formed literals to be treated as syntactically correct so instead
     * of only storing well-formed literals we hack around it this way.
     * N.B. This applies to any literal, not just XML well-formed literals.
    '''
    # private boolean wellformed = True

    '''
     * keeps the DatatypeFormatException if parsing failed for delayed
     * exception thrown in getValue()
    '''
    # private Throwable exception = None

    #=======================================================================
    # Constructors

    '''
     * Build a typed literal label from its lexical form. The
     * lexical form will be parsed now and the value stored. If
     * the form is not legal this will throw an exception.
     *
     * @param lex   the lexical form of the literal
     * @param lang  the optional language tag, only relevant for plain literals
     * @param dtype the type of the literal, None for old style "plain" literals
     * @throws DatatypeFormatException if lex is not a legal form of dtype
    '''
    def __init__(self, lex, value, dtype, lang, wellformed):
        self.lex = lex
        self.value = value
        self.dtype = dtype
        self.lang = lang
        self.wellformed = wellformed
        self.hash = 0

    # LiteralLabelImpl(String lex, String lang, RDFDatatype dtype) throws DatatypeFormatException:
    #     setLiteralLabel_1(lex, lang, dtype)
    

    # param String lex, String lang, RDFDatatype dtype
    # result void
    # throws DatatypeFormatException
    def setLiteralLabel_1(self, lex, lang, dtype):
        self.lexicalForm = lex
        self.dtype = dtype
        self.lang = "" if lang is None else lang
        if dtype is None:
            self.value = lex
        else:
            self.setValue(lex)
        
        self.normalize()
    

    '''
     * Build a typed literal label from its value form. If the value is a string we
     * assume this is intended to be a lexical form after all.
     *
     * @param value the value of the literal
     * @param lang  the optional language tag, only relevant for plain literals
     * @param dtype the type of the literal, None for old style "plain" literals
    '''
    # LiteralLabelImpl(Object value, String lang, RDFDatatype dtype) throws DatatypeFormatException:
    #     setLiteralLabel_2(value, lang, dtype)
    

    '''
     * Build a typed literal label from its value form using
     * whatever datatype is currently registered as the default
     * representation for this java class. No language tag is supplied.
     *
     * @param value the literal value to encapsulate
    '''
    # LiteralLabelImpl(Object value):
    #     RDFDatatype dt = TypeMapper.getInstance().getTypeByValue(value)
    #     if (dt is None):
    #         setWithNewDatatypeForValueClass(value)
    #      else:
    #         setLiteralLabel_2(value, "", dt)
        
    

    # param Object value
    # result void
    # def setWithNewDatatypeForValueClass(self, value):
    #     Class<?> c = value.getClass()
    #     #log.warn("inventing a datatype for " + c)
    #     RDFDatatype dt = AdhocDatatype(c)
    #     TypeMapper.getInstance().registerDatatype(dt)
    #     self.lang = ""
    #     self.dtype = dt
    #     self.value = value
    #     self.lexicalForm = value.toString()
    

    # param Object value, String language, RDFDatatype dtype
    # result void
    # throws DatatypeFormatException
    # def setLiteralLabel_2(self, value, language, dtype):
    #     # Constructor extraction: Preparation for moving into Node_Literal.
    #     self.dtype = dtype
    #     self.lang = (language is None ? "" : language)
    #     if (value instanceof String):
    #         String lex = (String) value
    #         lexicalForm = lex
    #         if (dtype is None):
    #             self.value = lex
    #          else:
    #             setValue(lex)
    #
    #      else:
    #         self.value = (dtype is None) ? value : dtype.cannonicalise(value)
    #
    #
    #     normalize()
    #
    #     if (dtype is not None and lexicalForm is None):
    #         # We are creating a literal from a java object, check the lexical form of the object is acceptable
    #         # Done here and uses self.dtype so it can use the normalized type
    #         wellformed = self.dtype.isValidValue(value)
    #         if (JenaParameters.enableEagerLiteralValidation and !wellformed):
    #             throw DatatypeFormatException(value.toString(), dtype, "in literal creation")
            
        
    

    '''
     * Old style constructor. Creates either a plain literal or an
     * XMLLiteral.
     *
     * @param xml If True then s is exclusive canonical XML of type rdf:XMLLiteral, and no checking will be invoked.
    '''
    # LiteralLabelImpl(String s, String lang, boolean xml):
    #     setLiteralLabel_3(s, lang, xml)
    

    # param String s, String lang, boolean xml
    # result void
    def setLiteralLabel_3(self, s, lang, xml):
        # Constructor extraction: Preparation for moving into Node_Literal.
        self.lexicalForm = s
        self.lang = "" if lang is None else lang
        if (xml):
            # XML Literal
            self.dtype = XMLLiteralType.theXMLLiteralType
            value = s
            wellformed = True
        else:
            # Plain literal
            self.value = s
            self.dtype = None
        
    

    '''
     * Internal function to set the object value from the lexical form.
     * Requires datatype to be set.
     *
     * @throws DatatypeFormatException if the value is ill-formed and
     *                                 eager checking is on.
    '''
    # param String lex
    # result void
    # throws DatatypeFormatException
    def setValue(self, lex):
        try:
            value = self.dtype.parse(lex)
            wellformed = True
        except DatatypeFormatException as e:
            if (JenaParameters.enableEagerLiteralValidation):
                #e.fillInStackTrace()
                raise e
            else:
                wellformed = False
                exception = e
            
        
    

    '''
     * Normalize the literal. If the value is narrower than the current data type
     * (e.g. value is xsd:date but the time is xsd:datetime) it will narrow
     * the type. If the type is narrower than the value then it may normalize
     * the value (e.g. set the mask of an XSDDateTime)
    '''
    # param 
    # result void
    def normalize(self):
        if (self.dtype is not None and self.value is not None):
            self.dtype = self.dtype.normalizeSubType(self.value, self.dtype)
        
    

    #=======================================================================
    # Methods

    '''
     * Answer True iff this is a well-formed XML literal.
    '''
    #@Override
    # param 
    # result boolean
    def isXML(self):
        #TODO
        return self.dtype == XMLLiteralType.theXMLLiteralType and self.wellformed
    

    '''
     * Answer True iff this is a well-formed literal.
    '''
    #@Override
    # param 
    # result boolean
    def isWellFormed(self):
        return self.dtype is not None and self.wellformed
    

    #@Override
    # param 
    # result boolean
    def isWellFormedRaw(self):
        return self.wellformed
    


    '''
     * Answer a human-acceptable representation of this literal value.
     * This is NOT intended for a machine-processed result.
    '''
    #@Override
    # param boolean quoting
    # result String
    def toString2(self, quoting):
        #StringBuilder
        b = StringBuilder('')
        if (quoting):
            b.append('"')
        #String
        lex = self.getLexicalForm()
        lex = Util.replace(lex, "\"", "\\\"")
        b.append(lex)
        if (quoting):
            b.append('"')
        if (self.lang is not None and self.lang != ""):
            b.append("@").append(self.lang)
        elif (self.dtype is not None):
            if (not self.dtype.equals(XSDDatatypeInstances.XSDstring)):
                b.append("^^").append(self.dtype.getURI())
        
        return b.toString()
    

    #@Override
    # param 
    # result String
    def toString(self):
        return self.toString2(False)
    

    '''
     * Answer the lexical form of this literal, constructing it on-the-fly
     * (and remembering it) if necessary.
    '''
    #@Override
    # param 
    # result String
    def getLexicalForm(self):
        if (self.lexicalForm is None):
            # (self.dtype is None ? value.toString() : dtype.unparse(value))
            self.lexicalForm = self.value.toString() if self.dtype is None else self.dtype.unparse(self.value)
        return self.lexicalForm
    

    '''
     * Answer an object used to index this literal. This object must provide
     *:@link Object#equals and:@link Object#hashCode based on values, not object
     * instance identity.
    '''
    #@Override
    # param 
    # result Object
    def getIndexingValue(self):
        if (self.isXML()):
            return self
        if (not self.lang.equals("")):
            #TODO 这里暂时做了替换
            #return self.getLexicalForm() + "@" + self.lang.toLowerCase(Locale.ROOT)
            return self.getLexicalForm() + "@" + self.lang
        if (self.wellformed):
            #Object
            value = self.getValue()
            # JENA-1936
            # byte[] does not provide hashCode/equals based on the contents of the array.
            #TODO 处理下面2行
            # if (value instanceof byte[])
            #     return ByteArray((byte[]) value)
            return value
        
        return self.getLexicalForm()
    

    '''
     *:@code byte[] wrapper that provides:@code hashCode and:@code equals based
     * on the value of the array. This assumes the:@code byte[] is not changed
     * (which is the case for literals with binary value).
    '''
    # static class ByteArray:
    #     private int hashCode = 0
    #
    #     private final byte[] bytes
    #
    #     '''package''' ByteArray(byte[] bytes):
    #         self.bytes = bytes
    #
    #
    #     #@Override
    # # param
    # # result int
    # def hashCode(self):
    #         if (hashCode == 0):
    #             final int prime = 31
    #             int result = 1
    #             hashCode = prime * result + Arrays.hashCode(bytes)
    #
    #         return hashCode
    #
    #
    #     #@Override
    # # param Object obj
    # # result boolean
    # def equals(self, obj):
    #         if (this == obj)
    #             return True
    #         if (obj is None)
    #             return False
    #         if (getClass() != obj.getClass())
    #             return False
    #         ByteArray other = (ByteArray) obj
    #         return Arrays.equals(bytes, other.bytes)
        
    

    '''
     * Answer the language associated with this literal (the empty string if
     * there's no language).
    '''
    #@Override
    # param 
    # result String
    def language(self):
        return self.lang
    

    '''
     * Answer a suitable instance of a Java class representing this literal's
     * value. May throw an exception if the literal is ill-formed.
    '''
    #@Override
    # param 
    # result Object
    # throws DatatypeFormatException
    def getValue(self):
        if (self.wellformed):
            return self.value
        else:
            raise DatatypeFormatException(self.lexicalForm, self.dtype, None)
            # throw DatatypeFormatException(
            #         self.lexicalForm,
            #         self.dtype,
            #         self.exception)
        
    

    '''
     * Answer the datatype of this literal, None if it is untyped.
    '''
    #@Override
    # param 
    # result RDFDatatype
    def getDatatype(self):
        return self.dtype
    

    '''
     * Answer the datatype URI of this literal, None if it untyped.
    '''
    #@Override
    # param 
    # result String
    def getDatatypeURI(self):
        if (self.dtype is None):
            return None
        return self.dtype.getURI()
    

    '''
     * Answer True iff this literal is syntactically equal to <code>other</code>.
     * Note: this is <i>not</i> <code>sameValueAs</code>.
    '''
    #@Override
    # param Object other
    # result boolean
    def equals(self, other):
        if (self == other):
            return True
        if (other is None or not isinstance(other, LiteralLabel)):
            return False
        
        #LiteralLabel
        otherLiteral = other

        #boolean
        #typeEquals = Objects.equals(dtype, otherLiteral.getDatatype())
        typeEquals = self.dtype == otherLiteral.getDatatype()
        if (not typeEquals):
            return False

        # Don't just use self.lexcialForm -- need to force delayed calculation from values.
        #boolean 
        #lexEquals = Objects.equals(getLexicalForm(), otherLiteral.getLexicalForm())
        lexEquals = self.getLexicalForm() == otherLiteral.getLexicalForm()
        if (not lexEquals):
            return False

        #boolean langEquals = Objects.equals(lang, otherLiteral.language())
        langEquals = self.lang == otherLiteral.language()
        if (not langEquals):
            return False
        # Ignore xml flag as it is calculated from the lexical form + datatype
        # Ignore value as lexical form + datatype -> value is a function.
        return True




    '''
     * Answer True iff this literal represents the same (abstract) value as
     * the other one.
    '''
    #@Override
    # param LiteralLabel other
    # result boolean
    def sameValueAs(self, other):
        return sameValueAs(self, other)
    
    # private int hash = 0

    '''
     * Answer the hashcode of this literal, derived from its value if it's
     * well-formed and otherwise its lexical form.
    '''
    #@Override
    # param 
    # result int
    def hashCode(self):
        # Literal labels are immutable.
        if (self.hash == 0):
            self.hash = self.getDefaultHashcode() if self.dtype is None else self.dtype.getHashCode(self)
        return self.hash
    

    '''
     * Answer the default hash value, suitable for datatypes which have values
     * which support hashCode() naturally: it is derived from its value if it is
     * well-formed and otherwise from its lexical form.
    '''
    #@Override
    # param 
    # result int
    def getDefaultHashcode(self):
        #return (wellformed ? value : getLexicalForm()).hashCode()
        if self.wellformed:
            return self.value.hashCode()
        else:
            return self.getLexicalForm().hashCode()